/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar;

import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.postgresql.util.PGobject;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
 * 表示数据库中的一行
 *
 * <p>
 * 这是一个简单的数据库记录封装，通过一个 Map 存储了数据表中的一行记录，
 * 注意只能通过字段名称来获取值，而不能通过字段编号(建表时或sql语句中的顺序)。
 * </p>
 * <p>
 * 这个类提供了一个通用的方法 getField(String name) 用于获取一个 Object，
 * 这个 Object 是通过 JDBC 转换后的对象，具体请参考 JDBC 类型映射文档。
 * 如果 name 不存在，则返回 null。
 * </p>
 * <p>
 * getField(String name) 虽然通用，但是不够具体，例如希望获取的是一个字符串，
 * 则需要强制将 getField() 的返回值转换为 String, 例如：
 * </p>
 * <pre>
 *     String value = (String) getField("name");
 * </pre>
 * <p>
 * 这有几个问题：
 * </p>
 * <ul>
 *   <li> 1. 程序看起来不够直接；</li>
 *   <li> 2. 如果 getField() 的返回值不是一个 String 对象或者 null，
 *           则会报告运行时错误；</li>
 *   <li> 3. ... </li>
 * </ul>
 * <p>
 * 因此，这个类也提供了 getString(), getInt() ... 等 getXXX() 方法，
 * 这些方法会返回具体的数据类型，并且提供更好的错误处理。
 * </p>
 */
public class JdbcRecord
{
	private static final Logger logger = LogManager.getLogger();
	private final Map<String, Object> fields;

	public JdbcRecord() {
		fields = new HashMap<>();
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder("{ ");
		for (String key : fields.keySet()) {
			if (builder.length() > 2) {
				builder.append(", ");
			}
			builder.append(key).append("=").append(fields.get(key).toString());
		}
		builder.append(" }");
		return builder.toString();
	}

	/**
	 * 获取字段值，注意返回一个 Object，可以表示任何类型，具体类型和数据库中定义一致。
	 *
	 * <p>
	 * 建议通过 getString()或其它 getXXX 方法来获取更具体的类型值，因为这些方法提供了
	 * 类型检测功能，如果数据类型不一致，会给出错误信息。
	 * </p>
	 *
	 * @param name 字段名称
	 * @return 字段值
	 */
	public Object getField(String name) {
		return fields.get(name);
	}

	/**
	 * 设置字段值
	 *
	 * @param name 字段名称
	 * @param value 字段值
	 */
	public void setField(String name, Object value) {
		if (name != null && value != null) {
			fields.put(name, value);
		}
	}

	/**
	 * 测试指定的字段是否存在.
	 *
	 * 字段不存在分几种情况：
	 *   1. 数据表中压根没有此字段；
	 *   2. 查询语句中没有列出此字段；
	 *   3. 此字段在数据表中的值为 null；
	 *
	 * @param name 字段名称
	 * @return 如果存在，返回 true， 否则返回 false
	 */
	public boolean hasField(String name) {
		return fields.containsKey(name);
	}

	/**
	 * 删除一个字段
	 *
	 * @param name 字段名称
	 */
	public void removeField(String name) {
		fields.remove(name);
	}

	/**
	 * 得到所有字段的名称
	 *
	 * @return 所有字段的名称集合
	 */
	public Set<String> FieldSet() {
		return fields.keySet();
	}

	/**
	 * 判断字段的类型
	 *
	 * @param name 字段名称
	 * @param klass 类型
	 * @return 对象, 或 null
	 */
	private Object getInstance(String name, Class<?> klass) {
		Object object = fields.get(name);
		if (object == null) {
			logger.warn("数据库字段[{}]不存在或为空.", name);
			return null;
		}
		if (!klass.isInstance(object)) {
			logger.error("数据库字段[{}]类型不是[{}]，而是[{}].",
					name, klass.getName(), object.getClass().getName());
			return null;
		}
		return object;
	}

	/**
	 * 返回 String 值
	 *
	 * @param name 字段名称
	 * @return 字符串值
	 */
	public String getString(String name) {
		Object object = this.getInstance(name, String.class);
		return (object == null) ? null : (String) object;
	}

	/**
	 * 返回 Integer 值
	 *
	 * @param name 字段名称
	 * @return 整数值
	 */
	public Integer getInteger(String name) {
		Object object = this.getInstance(name, Integer.class);
		return (object == null) ? null : (Integer) object;
	}

	/**
	 * 返回 Long 值
	 *
	 * @param name 字段名称
	 * @return 整数值
	 */
	public Long getLong(String name) {
		Object object = this.getInstance(name, Long.class);
		return (object == null) ? null : (Long) object;
	}

	/**
	 * 返回 Double 值
	 *
	 * @param name 字段名称
	 * @return Double 值
	 */
	public Double getDouble(String name) {
		Object object = this.getInstance(name, Double.class);
		return (object == null) ? null : (Double) object;
	}

	/**
	 * 返回 BigDecimal 值
	 *
	 * @param name 字段名称
	 * @return BigDecimal 值
	 */
	public BigDecimal getBigDecimal(String name) {
		Object object = this.getInstance(name, BigDecimal.class);
		return (object == null) ? null : (BigDecimal) object;
	}

	public Boolean getBoolean(String name) {
		Object object = this.getInstance(name, Boolean.class);
		return (object == null) ? null : (Boolean) object;
	}

	public boolean getBool(String name) {
		Boolean v = this.getBoolean(name);
		return (v == null) ? false : v;
	}

	/**
	 * 返回 Date 值
	 *
	 * @param name 字段名称
	 * @return Date 值
	 */
	public Date getDate(String name) {
		Object object = this.getInstance(name, Date.class);
		return (object == null) ? null : (Date) object;
	}

	/**
	 * 返回 Time 值
	 *
	 * @param name 字段名称
	 * @return Time 值
	 */
	public Time getTime(String name) {
		Object object = this.getInstance(name, Time.class);
		return (object == null) ? null : (Time) object;
	}

	/**
	 * 返回 Timestamp 值
	 *
	 * @param name 字段名称
	 * @return Timestamp 值
	 */
	public Timestamp getTimestamp(String name) {
		Object object = this.getInstance(name, Timestamp.class);
		return (object == null) ? null : (Timestamp) object;
	}

	/**
	 * 获取 JSON 对象
	 *
	 * @param name 字段名称
	 * @return JsonElement 对象，用 getAsJsonObject() / getAsJsonArray()
	 *   或类似的方法进行转换
	 */
	public JsonElement getJson(String name) {
		Object object = this.getInstance(name, PGobject.class);
		if (object == null) {
			return null;
		}
		PGobject pg_obj = (PGobject) object;
		if (!pg_obj.getType().equals("json")) {
			logger.error("数据库字段类型不是 json，而是[{}].", pg_obj.getType());
			return null;
		}
		JsonParser gson = new JsonParser();
		return gson.parse(pg_obj.getValue());
	}

	/**
	 * 克隆当前记录
	 */
	public JdbcRecord clone() {
		JdbcRecord record = new JdbcRecord();
		for (String key : fields.keySet()) {
			record.fields.put(key, fields.get(key));
		}
		return record;
	}

	/**
	 * 转换为 JSON 对象
	 */
	public JsonObject toJson() {
		JsonObject json_object = new JsonObject();

		for (String key : fields.keySet()) {
			json_object.addProperty(key, fields.get(key).toString());
		}
		return json_object;
	}
}
